/*
 *  This file is part of LifeBudget - Copyright 2010 Clifton Eaton
 *  ------------------------------------------------------------------
 *  LifeBudget is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  LifeBudget is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with LifeBudget.  If not, see <http://www.gnu.org/licenses/>.
 */
package org.lifebudget.app.client.mvc.view;

import java.util.ArrayList;
import java.util.List;

import org.lifebudget.app.client.events.AppEvents;
import org.lifebudget.app.client.mvc.model.BMF;
import org.lifebudget.app.client.mvc.model.Category;

import com.extjs.gxt.ui.client.Registry;
import com.extjs.gxt.ui.client.Style.HorizontalAlignment;
import com.extjs.gxt.ui.client.Style.Orientation;
import com.extjs.gxt.ui.client.Style.SelectionMode;
import com.extjs.gxt.ui.client.binding.FormBinding;
import com.extjs.gxt.ui.client.data.BeanModel;
import com.extjs.gxt.ui.client.data.ModelData;
import com.extjs.gxt.ui.client.event.ButtonEvent;
import com.extjs.gxt.ui.client.event.Events;
import com.extjs.gxt.ui.client.event.Listener;
import com.extjs.gxt.ui.client.event.MessageBoxEvent;
import com.extjs.gxt.ui.client.event.SelectionChangedEvent;
import com.extjs.gxt.ui.client.event.SelectionListener;
import com.extjs.gxt.ui.client.mvc.AppEvent;
import com.extjs.gxt.ui.client.mvc.Controller;
import com.extjs.gxt.ui.client.mvc.Dispatcher;
import com.extjs.gxt.ui.client.mvc.View;
import com.extjs.gxt.ui.client.store.ListStore;
import com.extjs.gxt.ui.client.store.Record;
import com.extjs.gxt.ui.client.store.Store;
import com.extjs.gxt.ui.client.store.StoreEvent;
import com.extjs.gxt.ui.client.store.StoreListener;
import com.extjs.gxt.ui.client.widget.ContentPanel;
import com.extjs.gxt.ui.client.widget.LayoutContainer;
import com.extjs.gxt.ui.client.widget.MessageBox;
import com.extjs.gxt.ui.client.widget.TabItem;
import com.extjs.gxt.ui.client.widget.Text;
import com.extjs.gxt.ui.client.widget.button.Button;
import com.extjs.gxt.ui.client.widget.form.FieldSet;
import com.extjs.gxt.ui.client.widget.form.FormPanel;
import com.extjs.gxt.ui.client.widget.form.TextArea;
import com.extjs.gxt.ui.client.widget.form.TextField;
import com.extjs.gxt.ui.client.widget.grid.ColumnConfig;
import com.extjs.gxt.ui.client.widget.grid.ColumnModel;
import com.extjs.gxt.ui.client.widget.grid.Grid;
import com.extjs.gxt.ui.client.widget.layout.FitLayout;
import com.extjs.gxt.ui.client.widget.layout.RowData;
import com.extjs.gxt.ui.client.widget.layout.RowLayout;
import com.extjs.gxt.ui.client.widget.toolbar.ToolBar;

/**
 * The Categories view contains the controls necessary for 
 * managing categories.  It generates a {@link LayoutContainer} 
 * which it adds to the Categories tab registered in the {@link Registry}.
 * 
 * The view provides basic CRUD operations and fires "refresh" events
 * when the categories are updated so that other views can update
 * their dropdown lists and other data that uses Categories.
 */
public class CategoriesView extends View {
	
	/** The center. */
	private LayoutContainer center;
	
	/** The categories tab. */
	private TabItem categoriesTab;
	
	/** The category details form bindings. */
	private FormBinding categoryDetailsFormBindings;
	
	/** The categories. */
	private List<Category> categories;
	
	/** The category manager panel. */
	private ContentPanel categoryManagerPanel;
	
	/** The category bean store. */
	private ListStore<BeanModel> categoryBeanStore;
	
	/** The category grid. */
	private Grid<BeanModel> categoryGrid;
	
	/** The category manager form. */
	private FormPanel categoryManagerForm;
	
	/** The name field. */
	private TextField<String> nameField;
	
	/** The description field. */
	private TextArea descriptionField;

	/**
	 * Instantiates a new categories view.
	 *
	 * @param controller the controller
	 */
	public CategoriesView(Controller controller) {
		super(controller);

	}

	/* (non-Javadoc)
	 * @see com.extjs.gxt.ui.client.mvc.View#initialize()
	 */
	public void initialize() {

	}

	/**
	 * Handles the <code>Init</code> event, and constructs the categories view
	 * with the data provided by the event.
	 */
	public void initUI() {
		categoriesTab = (TabItem) Registry.get(TabView.CATEGORIES_TAB);
		center = (LayoutContainer) Registry.get(AppView.CENTER_PANEL);

		createCategoryManagerPanel();

		categoriesTab.add(categoryManagerPanel);
		center.layout();
	}

	/**
	 * Utility method to create the category manager panel during initialization.
	 */
	private void createCategoryManagerPanel() {
		// Content Panel container
		categoryManagerPanel = new ContentPanel(new FitLayout());
		// categoryManagerPanel.setHeading("Manage Categories");
		categoryManagerPanel.setHeaderVisible(false);
		categoryManagerPanel.setFrame(false);
		// row Widget layout container
		RowLayout rLayout = new RowLayout(Orientation.HORIZONTAL);
		LayoutContainer rowWidget = new LayoutContainer(rLayout);
		rowWidget.setSize(850, 350);

		createCategoryManagerGrid();

		rowWidget.add(categoryGrid, new RowData(.59, 1));
		rowWidget.add(new Text(), new RowData(.01, 1));

		createCategoryManagerForm();

		FieldSet fs = new FieldSet();
		// fs.setLayout(new FitLayout());
		fs.add(categoryManagerForm);
		fs.setHeading("Category Details");
		rowWidget.add(fs, new RowData(.4, 1));

		categoryManagerPanel.add(rowWidget);

		// create toolbar
		createCategoryManagerToolBar(categoryManagerPanel);

		archiveButton.disable();

		// Create buttons on the contentpanel
		categoryManagerPanel.setButtonAlign(HorizontalAlignment.CENTER);
		categoryManagerPanel.addButton(new Button("Reset", new SelectionListener<ButtonEvent>() {
			@Override
			public void componentSelected(ButtonEvent ce) {
				handleResetButtonClicked();
			}

		}));
		categoryManagerPanel.addButton(new Button("Commit", new SelectionListener<ButtonEvent>() {
			@Override
			public void componentSelected(ButtonEvent ce) {
				handleCommitButtonClicked();
			}

		}));
	}

	/**
	 * Handle reset button clicked.
	 */
	private void handleResetButtonClicked() {
		List<BeanModel> models = categoryBeanStore.getModels();
		for (BeanModel beanModel : models) {
			if ((Long) beanModel.get("id") == 0) {
				categoryBeanStore.remove(beanModel);
			}
		}
		categoryGrid.getStore().rejectChanges();
	}

	/**
	 * Handle commit button clicked.
	 */
	private void handleCommitButtonClicked() {
		categoryGrid.getStore().commitChanges();
	}

	/**
	 * Creates the category manager tool bar.
	 *
	 * @param categoryManagerPanel the category manager panel
	 */
	private void createCategoryManagerToolBar(ContentPanel categoryManagerPanel) {
		// Create Toolbar
		ToolBar toolBar = new ToolBar();
		addButton = new Button("Add Category");
		addButton.addSelectionListener(new SelectionListener<ButtonEvent>() {
			@Override
			public void componentSelected(ButtonEvent ce) {
				handleAddCategoryButtonClicked();
			}
		});
		archiveButton = new Button("Archive Selected");
		archiveButton.addSelectionListener(new SelectionListener<ButtonEvent>() {

			@Override
			public void componentSelected(ButtonEvent ce) {
				handleArchiveCategoryButtonClicked();
			}

		});
		toolBar.add(addButton);
		toolBar.add(archiveButton);

		categoryManagerPanel.setTopComponent(toolBar);
	}

	/** The new category. */
	private BeanModel newCategory = null;

	/**
	 * Handle add category button clicked.
	 */
	private void handleAddCategoryButtonClicked() {
		if (newCategory != null) {
			// new row already exists
			categoryGrid.getSelectionModel().select(newCategory, false);
			categoryDetailsFormBindings.bind(newCategory);
			nameField.focus();
		} else {
			Category c = new Category();
			c.setArchived(false);
			newCategory = BMF.getCategoryFactory().createModel(c);

			categoryBeanStore.add(newCategory);
			Record r = categoryBeanStore.getRecord(newCategory);
			r.set("name", "New Category");

			categoryGrid.getSelectionModel().select(newCategory, false);
			categoryDetailsFormBindings.bind(newCategory);
			nameField.focus();
			newCategory = null;
		}
	}

	/**
	 * Handle archive category button clicked.
	 */
	private void handleArchiveCategoryButtonClicked() {
		// GWT.log("Archive Category Button Clicked");
		Category category = (Category) categoryGrid.getSelectionModel().getSelectedItem().getBean();
		String msg = "Are you sure you wish to permanently archive " + "the category " + category.getName()
				+ "? Any category amounts in a paycheck calculator referencing this category will be removed.";
		MessageBox.confirm("Delete Category?", msg, new Listener<MessageBoxEvent>() {

			@Override
			public void handleEvent(MessageBoxEvent be) {
				Button button = be.getButtonClicked();

				if (button.getText().equals("Yes")) {
					BeanModel bm = categoryGrid.getSelectionModel().getSelectedItem();
					categoryBeanStore.remove(bm);
					// TODO change c to category and make final?
					Category c = (Category) bm.getBean();
					c.setArchived(true);
					fireEvent(new AppEvent(AppEvents.CategoryArchived, c));
				}

			}
		});

	}

	/**
	 * Creates the category manager form.
	 */
	private void createCategoryManagerForm() {
		categoryManagerForm = new FormPanel();
		categoryManagerForm.setHeaderVisible(false);

		nameField = new TextField<String>();
		nameField.setName("name");
		nameField.setFieldLabel("Name");
		nameField.setAllowBlank(false);
		nameField.setMaxLength(16);
		nameField.setAutoValidate(true);
		categoryManagerForm.add(nameField);

		descriptionField = new TextArea();
		descriptionField.setName("description");
		descriptionField.setFieldLabel("Description");
		descriptionField.setAutoValidate(true);
		categoryManagerForm.add(descriptionField);

		categoryDetailsFormBindings = new FormBinding(categoryManagerForm, true);
		categoryDetailsFormBindings.setStore((Store<BeanModel>) categoryGrid.getStore());
		disableCategoryForm();
	}

	/**
	 * Creates the category manager grid.
	 */
	private void createCategoryManagerGrid() {

		List<ColumnConfig> configs = new ArrayList<ColumnConfig>();

		ColumnConfig column = new ColumnConfig();
		column.setId("name");
		column.setHeader("Category");
		column.setWidth(240);
		configs.add(column);

		column = new ColumnConfig();
		column.setId("description");
		column.setHeader("Description");
		column.setWidth(240);
		configs.add(column);

		createCategoryManagerStore();
		ColumnModel cm = new ColumnModel(configs);

		categoryGrid = new Grid<BeanModel>(categoryBeanStore, cm);
		categoryGrid.getView().setEmptyText("No Categories Defined");
		categoryGrid.setBorders(true);
		// categoryGrid.setAutoExpandColumn("name");

		categoryGrid.getSelectionModel().setSelectionMode(SelectionMode.SINGLE);
		categoryGrid.getSelectionModel().addListener(Events.SelectionChange,
				new Listener<SelectionChangedEvent<BeanModel>>() {
					public void handleEvent(SelectionChangedEvent<BeanModel> be) {
						if (be.getSelection().size() > 0) {
							handleCategorySelected(be);
						} else {
							handleCategorySelectionCleared();
						}
					}

				});
	}

	/**
	 * Handle category selection cleared.
	 */
	private void handleCategorySelectionCleared() {
		archiveButton.disable();
		categoryDetailsFormBindings.unbind();
	}

	/**
	 * Handle category selected.
	 *
	 * @param be the button event
	 */
	private void handleCategorySelected(SelectionChangedEvent<BeanModel> be) {
		archiveButton.enable();
		enableCategoryForm();
		categoryDetailsFormBindings.bind((ModelData) be.getSelection().get(0));
	}

	/**
	 * Enable category form.
	 */
	private void enableCategoryForm() {
		nameField.enable();
		descriptionField.enable();

	}

	/**
	 * Disable category form.
	 */
	private void disableCategoryForm() {
		nameField.disable();
		descriptionField.disable();
	}

	/** The batch commit collection. */
	private List<Category> batchCommit = new ArrayList<Category>();
	
	/** The archive button. */
	private Button archiveButton;
	
	/** The add button. */
	private Button addButton;

	/**
	 * Execute a commit on the category store. Collects all of 
	 * the modified records into a single batch commit.
	 *
	 * @param se the store event
	 */
	public void doCommit(StoreEvent se) {
		BeanModel bean = (BeanModel) se.getModel();
		batchCommit.add((Category) bean.getBean());
		if (se.getStore().getModifiedRecords().size() == 0) {
			// call the batchService
			fireEvent(new AppEvent(AppEvents.CategoryChangesCommitted, batchCommit));
			// clear the batch list for next time.
			batchCommit = new ArrayList<Category>();
		}

	}

	/**
	 * Creates the category manager store and populates with data from the event.
	 */
	private void createCategoryManagerStore() {
		categoryBeanStore = new ListStore<BeanModel>();
		categoryBeanStore.setMonitorChanges(true);
		categoryBeanStore.addStoreListener(new StoreListener<BeanModel>() {

			@Override
			public void storeUpdate(StoreEvent<BeanModel> se) {
				if (se.getOperation() != null && se.getOperation().equals(Record.RecordUpdate.COMMIT)) {
					doCommit(se);
				}
			}

		});

		List<BeanModel> beans = BMF.getCategoryFactory().createModel(categories);
		categoryBeanStore.add(beans);
	}

	/* (non-Javadoc)
	 * @see com.extjs.gxt.ui.client.mvc.View#handleEvent(com.extjs.gxt.ui.client.mvc.AppEvent)
	 */
	@Override
	protected void handleEvent(AppEvent event) {
		if (event.getType() == AppEvents.Init) {
			// GWT.log("CategoriesView handling Init");
			categories = (List<Category>) event.getData();
			// if the data is null, create an empty collection of Categories
			if (categories == null) {
				categories = new ArrayList<Category>();
			}
			initUI();
		}
		if (event.getType() == AppEvents.CategoryChangesCommitted) {
			categories = (List<Category>) event.getData();
			List<BeanModel> beans = BMF.getCategoryFactory().createModel(categories);
			categoryBeanStore.removeAll();
			categoryBeanStore.add(beans);
			// TODO I don't like dispatching events from a view
			AppEvent e = new AppEvent(AppEvents.RefreshCategory);
			e.setData(categories);
			Dispatcher.get().dispatch(e);
		}

	}

}
